Skip to content

feat: support Cursor SDK model params in proof - #178

Merged
tonyketcham merged 14 commits into
mainfrom
toeknee/proof-model-params-da19
May 11, 2026
Merged

feat: support Cursor SDK model params in proof#178
tonyketcham merged 14 commits into
mainfrom
toeknee/proof-model-params-da19

Conversation

@tonyketcham

@tonyketcham tonyketcham commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Summary of changes

Adds SDK-style model selections to Proof DAG/model-file config so complexity mappings specify { id, params? }, validates those selections against Cursor.models.list() at run time, expands partial param selections to valid preset variants before creating Cursor SDK agents, removes the deprecated createModelResolver helper, and migrates checked-in Proof configs/examples to object-based selections.

Closes #

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • I added doc comments to any new public exports, and inline comments to any hard-to-understand areas
  • My changes generate no new console errors locally
  • If applicable, try to include a test that fails without this PR but passes with it

Does this introduce any non-backwards compatible changes?

  • Yes
    • Proof model overrides now require object selections like { "id": "composer-2" }
  • No

Does this include any user config changes?

  • Yes
    • If so, I have updated the relevant areas of documentation
  • No
Open in Web Open in Cursor 

cursoragent and others added 2 commits May 8, 2026 06:58
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
@tonyketcham
tonyketcham marked this pull request as ready for review May 8, 2026 14:22
- Add @deprecated JSDoc to createModelResolver (silently discards params)
- Render modelSelection.params in canvas template (was serialized but unused)
- Fix variant tiebreaker: prefer catalog default on score ties
- De-duplicate defaultVariant() call in chooseMatchingVariant

Change-Id: Ic8dfe2ddc37948affb1b6849428f0a9439daa170

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Found 2 medium-risk issues in the new model-selection support:

  1. parseDAG() / validateModelMap() now canonicalize string model overrides into { id } objects, which changes the observable return shape of the exported parsing helpers for string-only inputs.
  2. Param validation assumes Cursor.models.list() always populates parameters, but the SDK marks that field optional, so models that expose only variants would reject otherwise valid param selections.
Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/dag.ts Outdated
Comment thread packages/proof/src/dag.ts Outdated
cursoragent and others added 2 commits May 10, 2026 20:19
…alogs

- Keep string model entries as strings in validateModelSelection output so
  parseDAG/validateModelMap stay shape-stable for legacy configs; normalize
  to ModelSelection only via normalizeModelSelection.
- When Cursor.models.list() omits parameters but defines variants, validate
  explicit params by matching a preset variant instead of rejecting supported
  selections.

Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Comment thread packages/proof/README.md Outdated
} from '@flatbread/proof';
```

Note: `createModelResolver` is deprecated in favor of `createModelSelectionResolver` when you need param support (the deprecated helper only returns `ModelSelection.id` and drops `params`).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need for keeping the deprecated function? None of Proof has been released yet so we can change the API however we'd like

Comment thread packages/proof/src/dag.ts Outdated
/**
* Validate a JSON model override without changing its nominal shape: plain
* strings stay strings so `parseDAG` / `validateModelMap` remain
* round-trip-stable for legacy configs. Use `normalizeModelSelection` when

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If necessary, migrate any checked-in legacy configs so we can remove that bit of the comment. Ideally we should avoid legacy config support since Proof has not been released.

Comment thread packages/proof/src/dag.ts
* round-trip-stable for legacy configs. Use `normalizeModelSelection` when
* you need a `ModelSelection` object (including `{ id }` for strings).
*/
export function validateModelSelection(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is a bit dense, let's break it up into some semantic util functions to make it easier to comprehend

cursoragent and others added 2 commits May 11, 2026 00:04
Remove the deprecated model resolver, require object-based model selections in DAG configs, migrate checked-in examples, and split model validation into smaller helpers.

Tests:
- pnpm -F @flatbread/proof typecheck
- pnpm -F @flatbread/proof build
- pnpm lint
- node --input-type=module runtime probes for validateModelMap/createModelSelectionResolver
- node --input-type=module parseDAG checks for checked-in proof examples

Co-authored-by: Tony <tonyketcham@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Code Review — feat: support Cursor SDK model params in proof

This is a well-structured feature. The separation of concerns across validation, normalization, catalog-backed resolution, and variant scoring is clean, defensive cloning throughout prevents aliasing bugs, and the eager validation loop at startup (for (const complexity of COMPLEXITY_KEYS)) fails fast before any agent work begins. The --init-only bypass for the SDK call is correctly documented. Good work overall.

Actionable feedback below (no blockers):

Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/run_dag.ts
Comment thread packages/proof/src/dag.ts Outdated
Comment thread packages/proof/src/dag.ts
Comment thread packages/proof/src/canvas_writer.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Review verdict

REQUEST_CHANGES — one implementation/doc contradiction that will silently break existing --models-file files, one unsupported breaking public-API removal, no error handling on the new startup network call, and zero test coverage for 222+ lines of new branching logic that is correctness-critical.


Chunk-bound feedback

packages/proof/src/dag.ts:511–514

  • severity: HIGH
  • flagged-by: correctness-and-contracts, proof-runtime-internals
  • risk: validateModelMap calls validateModelSelection, which calls validateModelSelectionObject and rejects any non-object input with "must be a model object." A user's existing --models-file or inline DAG models that uses the old plain-string format ("HIGH": "claude-opus-4-7") will throw at parse time with a cryptic error, not a migration notice. normalizeModelSelection was introduced precisely to accept string | ModelSelection — it is the right function for the parse path.
  • minimal fix: Replace the validateModelSelection(value, ...) call in validateModelMap with normalizeModelSelection(value as ModelSpec, ...) so the function accepts both the legacy string format and the new object format.

packages/proof/src/index.ts:14

  • severity: HIGH
  • flagged-by: correctness-and-contracts
  • risk: createModelResolver is removed from the public exports of @flatbread/proof with no deprecation shim. Any external consumer importing it (including the orchestrator DAG files and any user-authored tooling) gets a TypeScript compile error with no actionable message. The return type also changed from (c) => string to (c) => ModelSelection, which is an additional silent breakage for consumers who stored the inferred return type.
  • minimal fix: Re-export createModelResolver as a deprecated alias: export const createModelResolver = createModelSelectionResolver; with a @deprecated JSDoc, and bump the minor version with a migration note.

packages/proof/src/run_dag.ts:528–543

  • severity: MED
  • flagged-by: proof-runtime-internals
  • risk: await Cursor.models.list() is called at the top of main() with no try/catch. A transient network error, a rate-limit response, or a missing CURSOR_API_KEY at this point kills the entire proof run before any task has executed. For long DAGs (e.g. the PMF audit) this means paying the startup cost with nothing to show for it. The existing CURSOR_API_KEY check two lines above fires before we even reach this code, so the failure mode here is specifically network/rate-limit transience.
  • minimal fix: Wrap the catalog fetch in a try/catch; on failure, warn to stderr ([proof] WARNING: catalog validation skipped — Cursor.models.list() failed: <err.message>) and fall back to unresolvedModelForComplexity so the DAG can still launch with unvalidated model ids.

.cursor/skills/proof/SKILL.md:176

  • severity: MED
  • flagged-by: correctness-and-contracts, proof-runtime-internals
  • risk: Line 176 states "Values can be plain SDK model id strings or SDK model selections with params", but line 58 in the same file states "Values must be model selection objects." The implementation (validateModelMap) only accepts objects. One of these statements must be wrong — and if the intent is that strings should work (as line 176 and the README imply), then the code has the bug described above in dag.ts:511–514.
  • minimal fix: Once dag.ts:511–514 is fixed to use normalizeModelSelection, update line 58 to match line 176: both formats are valid.

Coverage plan

  1. packages/proof/src/__tests__/dag.test.tspositive: validateModelMap accepts plain string values ({"HIGH": "claude-opus-4-7"}) after the normalizeModelSelection fix
  2. packages/proof/src/__tests__/dag.test.tspositive: validateModelMap accepts full object values ({"HIGH": {"id": "claude-opus-4-7", "params": [...]}})
  3. packages/proof/src/__tests__/dag.test.tsnegative: validateModelSelection rejects null, array, missing id, empty string id
  4. packages/proof/src/__tests__/dag.test.tsnegative: validateModelParams rejects duplicate param ids; rejects non-array params
  5. packages/proof/src/__tests__/dag.test.tspositive: normalizeModelSelection converts a plain string to {id: string} and passes an existing object through unchanged
  6. packages/proof/src/__tests__/dag.test.tsedge (unknown model): resolveModelSelectionFromCatalog throws with the full known-models list when the id is not in the catalog
  7. packages/proof/src/__tests__/dag.test.tspositive: resolveModelSelectionFromCatalog returns clone of selection for a model with no variants
  8. packages/proof/src/__tests__/dag.test.tspositive: resolveModelSelectionFromCatalog picks the default variant when no params are requested
  9. packages/proof/src/__tests__/dag.test.tsedge (partial params): chooseMatchingVariant scores variants by closeness-to-defaults for un-specified params and picks the best-fit; tie-breaks to the catalog-declared default
  10. packages/proof/src/__tests__/dag.test.tsnegative: resolveModelSelectionFromCatalog throws when requested params match no variant
  11. packages/proof/src/__tests__/dag.test.tspositive: createCatalogBackedModelResolver returns a cloned (not the same reference) ModelSelection on repeated calls for the same complexity (cache isolation)
  12. packages/proof/src/__tests__/dag.test.tsnegative: resolveModelSelectionFromCatalog throws when a model has neither parameters nor variants and params are requested

Suggested follow-ups

  • Consider whether --models-file should support a "$schema" field pointing to a generated JSON Schema for model selections, so editors can validate the file before running proof.
  • The scoreVariant heuristic (prefer defaults for un-specified params) is not documented in a comment; add an inline explanation so future maintainers understand why the scoring works the way it does.

Reviewer scoreboard

  • correctness-and-contracts: 3 findings, 0 coverage gaps, signal: HIGH
  • proof-runtime-internals: 2 findings, 0 coverage gaps, signal: HIGH
  • test-coverage-robustness: 0 non-test findings, 12 coverage gaps, signal: HIGH
Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/dag.ts Outdated
Comment thread packages/proof/src/run_dag.ts Outdated
? unresolvedModelForComplexity
: createCatalogBackedModelResolver(
unresolvedModelForComplexity,
await Cursor.models.list()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error handling around Cursor.models.list() — a transient failure here kills the entire run before any task executes.

The CURSOR_API_KEY guard a few lines above only checks for a missing env var; it does not protect against network errors, rate limits, or SDK-internal failures from the catalog fetch itself. For long-running DAGs (PMF audit, schema migration), failing at startup with no work done is a bad failure mode.

Suggested approach:

let catalog: readonly ModelCatalogItem[];
try {
  catalog = await Cursor.models.list();
} catch (err) {
  console.warn(
    `[proof] WARNING: catalog validation skipped — Cursor.models.list() failed: ${
      err instanceof Error ? err.message : String(err)
    }`
  );
  catalog = [];
}

When catalog is empty, resolveModelSelectionFromCatalog will throw for every model id — so you would also need to skip the eager validation loop that follows, or only run it when the catalog is non-empty.

Comment thread .cursor/skills/proof/SKILL.md
Comment thread packages/proof/src/index.ts
cursoragent and others added 6 commits May 10, 2026 18:53
- add tie-break and no-match tests for variant resolution

- clarify legacy modelSelection fallback invariant in run_dag

- rename generic non-empty string validator for readability

- annotate duplicated canvas template model types

Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Change-Id: I5067d4783d7c36be74038a9dc4c860b4ef0b190f
…tion

- Changed model selection format to accept both plain SDK model id strings and model selection objects with parameters.
- Updated examples in SKILL.md and README.md to reflect new model id usage.
- Enhanced validation functions to support mixed model selection shapes.
- Added tests for new model selection behaviors and validation logic.

Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Change-Id: Ibed4c110903ded9b94630228eeeb40071822fc82
Change-Id: Id88c02cf45a8fc0206e69deedec0253735245187

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Code Review — feat: support Cursor SDK model params in proof

Verdict: REQUEST_CHANGES — Two independent consensus HIGH findings across all three reviewer perspectives: (1) SKILL.md:176 declares string values valid for the models field while validateModelMap calls validateModelSelection which rejects non-objects, creating a published-doc / enforced-contract break; (2) Cursor.models.list() is called bare at startup with no error handling, meaning a transient network error kills the entire run before any task executes.


Chunk-bound feedback

packages/proof/src/dag.ts:511-514 — severity: HIGH — flagged-by: all three perspectives

validateModelMap calls validateModelSelection, which throws "must be a model object" for any plain string. SKILL.md:176 explicitly tells users "Values can be plain SDK model id strings." A user who follows the docs will receive a runtime error with no indication the doc is wrong.

Minimal fix (pick one and apply it everywhere):

  • Option A — "objects only" (least code change): keep validateModelSelection but remove the "plain SDK model id strings" clause from SKILL.md:176, and update SKILL.md:58 to say "must be model selection objects" consistently.
  • Option B — "strings or objects": change validateModelMap to call normalizeModelSelection(value, ...) instead, which accepts both, then fix SKILL.md:58 to match.

Either way, add a negative test: validateModelMap({ HIGH: "claude-opus-4-7", ... }) should throw (option A) or pass (option B), pinning whichever contract is chosen.


packages/proof/src/run_dag.ts:530-543 — severity: HIGH — flagged-by: proof-runtime-internals, test-coverage-robustness

await Cursor.models.list() is called unconditionally with no try/catch. A transient network hiccup, rate-limit response, or expired CURSOR_API_KEY causes an unhandled rejection that terminates the process before a single task runs, with no recovery path and no user-actionable message.

Minimal fix: Wrap in try/catch and throw a clear error: "Could not fetch Cursor model catalog — check CURSOR_API_KEY and network connectivity. Original error: ...".


packages/proof/src/canvas_writer.ts:236-246 — severity: MED — flagged-by: proof-runtime-internals

ModelParameterValue and ModelSelection are redeclared inside the canvas template with only a // Keep in sync comment as guard. TypeScript cannot inspect the embedded string template. If dag.ts adds a field to either interface, the canvas will compile fine but silently render stale data.

Minimal fix: Add a type-level assertion in canvas_writer.ts (outside the template string): type _AssertModelSelectionSync = import('./dag.js').ModelSelection extends ModelSelection ? true : never; to make divergence a compile error. Long-term: extract the template into a file that can import from dag.ts directly.


Coverage plan (critical gaps)

  1. packages/proof/src/dag.test.tsnegativevalidateModelMap({ HIGH: "claude-opus-4-7", ... }) behavior must be pinned (throw or not) based on the chosen contract.
  2. packages/proof/src/dag.test.tspositivevalidateModelMap({ HIGH: { id: 'x' }, MED: { id: 'y' }, LOW: { id: 'z' } }) round-trips correctly (zero tests for the happy path).
  3. packages/proof/src/dag.test.tsnegativevalidateModelSelection({}) throws with label-prefixed message; validateModelSelection({ id: '' }) throws; validateModelSelection({ id: 'x', params: 'bad' }) throws.
  4. packages/proof/src/dag.test.tspositivecreateModelSelectionResolver() with no overrides returns DEFAULT_MODEL_MAP[c] shape for each of HIGH/MED/LOW.
  5. packages/proof/src/dag.test.tspositivecreateModelSelectionResolver({ HIGH: { id: 'x' } }) returns override for HIGH and defaults for MED/LOW.
  6. packages/proof/src/dag.test.tspositiveformatModelSelection({ id: 'x' })'x'; with params → 'x (effort=max)'.
  7. packages/proof/src/dag.test.tspositivecreateCatalogBackedModelResolver resolves all three complexities; propagates error on unknown model id.
  8. packages/proof/src/dag.test.tsedge:empty-params-strippingnormalizeModelSelection({ id: 'x', params: [] }) returns { id: 'x' } with no params key.
  9. packages/proof/src/dag.test.tsedge:duplicate-paramsnormalizeModelSelection with two params sharing the same id throws with /duplicate id/.
  10. packages/proof/src/dag.test.tsedge:cache-isolation — second call to createCatalogBackedModelResolver for the same complexity is deepEqual but not === the first call.
  11. packages/proof/src/dag.test.tsedge:legacy-resume-pathtaskModelSelection with ts.modelSelection absent and ts.model: "claude-opus-4-7" produces { id: "claude-opus-4-7" }.
  12. packages/proof/src/dag.test.tsedge:both-declaredresolveModelSelectionFromCatalog for a catalog item with both parameters and variants resolves via the variant path.

Suggested follow-ups (out of scope)

  • --models-file parsing path: Confirm it also calls validateModelMap (or equivalent) and not an older string-only parser.
  • SDK params runtime handling: TypeScript structural compatibility is confirmed, but whether the SDK actually sends params to the inference endpoint is not testable from this repo. Add an integration note or smoke test once the SDK behavior is confirmed.
  • Cursor.models.list() retry policy: Once the try/catch is added, evaluate whether one retry with exponential backoff is warranted for this startup-blocking call.
  • canvas_writer.ts long-term: Extract the template into a separate file that can import from dag.ts directly.

Reviewer scoreboard

  • proof-runtime-internals: 3 findings, 5 coverage gaps, signal: HIGH
  • correctness-and-contracts: 3 findings, 6 coverage gaps, signal: HIGH
  • test-coverage-robustness: 2 findings, 14 coverage gaps, signal: MED
Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/dag.ts
Comment thread packages/proof/src/run_dag.ts Outdated
? unresolvedModelForComplexity
: createCatalogBackedModelResolver(
unresolvedModelForComplexity,
await Cursor.models.list()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor.models.list() called bare with no error handling — run-killing reliability gap (consensus HIGH, flagged by two perspectives).

A transient network error, expired CURSOR_API_KEY, or rate-limit response here causes an unhandled rejection that terminates the entire process before a single task executes. The CURSOR_API_KEY guard a few lines above only checks for an empty string — it does not protect against runtime SDK failures.

Minimal fix:

let catalog: Awaited<ReturnType<typeof Cursor.models.list>>;
try {
  catalog = await Cursor.models.list();
} catch (err) {
  throw new Error(
    `Could not fetch Cursor model catalog — check CURSOR_API_KEY and network connectivity. Original error: ${err}`
  );
}

type TaskKind = 'task' | 'pause' | 'oracle';

// Keep in sync with ModelParameterValue / ModelSelection in dag.ts.
interface ModelParameterValue {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ModelParameterValue and ModelSelection redeclared without compile-time sync enforcement (MED).

The // Keep in sync comment is the only guard. If dag.ts adds a required field to ModelSelection, TypeScript will not warn here — the local interface shadows any import, and the embedded template string is not typechecked by the package's own tsconfig. Silent canvas rendering anomalies would be the only symptom.

Minimal fix: add a structural assertion outside the template string:

type _AssertModelSelectionInSync =
  import('./dag.js').ModelSelection extends ModelSelection ? true : never;

This turns any structural divergence into a compile error that the package's own typecheck script will catch.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Review verdict

REQUEST_CHANGES — two prior findings remain unaddressed: (1) Cursor.models.list() is called without error handling, meaning any transient SDK/network failure kills the entire DAG run before a single task starts; (2) ModelParameterValue and ModelSelection are still duplicated verbatim inside the canvas template string with only a comment as the sync guard.


Good progress since last run

  • validateModelId correctly renamed to validateNonEmptyString — removes misleading specificity.
  • taskModelSelection legacy fallback now carries an explanatory comment; the ts.model assumption (always a bare id in pre-this-PR persisted state) is documented.
  • Tie-breaking tests added: highest-scoring variant wins, equal-score tie breaks to catalog default. Both paths covered.
  • validateModelMap now routes through normalizeModelSelection, accepting both plain string and object inputs.
  • SKILL.md documentation contradiction resolved — "can be plain strings or objects" is used consistently.

Chunk-bound feedback

packages/proof/src/run_dag.ts:534

  • severity: HIGH
  • flagged-by: proof-runtime-internals
  • risk: A network timeout, rate-limit response, or SDK-internal error from Cursor.models.list() propagates as an unhandled rejection, terminating the process and discarding the DAG entirely — no partial results, no resumable state, no user-readable error.
  • minimal fix: Wrap in try/catch; on error, log a warning and fall back to unresolvedModelForComplexity (skipping catalog validation), or at minimum surface a clear error message before exiting rather than a raw stack trace.

packages/proof/src/canvas_writer.ts:236–244

  • severity: MED
  • flagged-by: proof-runtime-internals
  • risk: ModelParameterValue and ModelSelection are duplicated inside the embedded canvas template string. TypeScript does not enforce their parity with the exported types in dag.ts. A field added to ModelSelection (e.g., a displayName hint for the canvas) will silently be absent from canvas rendering.
  • minimal fix: Add a codegen or satisfies-style sanity check, or at minimum expand the // Keep in sync comment to list the exact fields that need mirroring so a reviewer can spot drift at a glance.

Coverage plan

  1. packages/proof/src/dag.test.tspositivecreateCatalogBackedModelResolver cache hit: call resolver('HIGH') twice with the same catalog; assert resolveModelSelectionFromCatalog is only invoked once (verify via a spy or by injecting a catalog that would throw on second lookup).
  2. packages/proof/src/dag.test.tsnegativecreateCatalogBackedModelResolver propagates catalog errors: pass a catalog that throws for one complexity; assert the resolver surfaces the error on first call and does not cache a bad state.
  3. packages/proof/src/dag.test.tsedgeCursor.models.list() transient failure: mock the SDK call to reject; assert the run exits with a readable message rather than an uncaught rejection.

Reviewer scoreboard

  • proof-runtime-internals: 2 findings, 2 coverage gaps, signal: HIGH
  • test-coverage-robustness: 0 findings, 1 coverage gap, signal: MED
  • correctness-and-contracts: 0 new findings (ModelMap shape change intentional and documented; Proof unreleased), signal: LOW
Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/run_dag.ts Outdated
type Complexity = 'HIGH' | 'MED' | 'LOW';
type TaskKind = 'task' | 'pause' | 'oracle';

// Keep in sync with ModelParameterValue / ModelSelection in dag.ts.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Canvas template types still duplicated — silent drift risk (prior finding, still open).

The ModelParameterValue and ModelSelection interfaces here live inside the embedded canvas template string, so TypeScript cannot enforce their parity with the exported types in dag.ts. The // Keep in sync comment is the only guard.

A field added to the public ModelSelection (or a param renamed) will silently be invisible in canvas rendering until someone notices at runtime.

One pragmatic option: add a prose list of the fields being mirrored to the comment so a code reviewer has a checklist:

// Keep in sync with ModelParameterValue / ModelSelection in dag.ts.
// Fields mirrored: ModelParameterValue.{id, value}; ModelSelection.{id, params?}.
// If either type gains a field, update both this template copy and the canvas render below.

A stronger option (if the template compilation step is ever added) is to import and satisfies-assert the types before embedding.

Change-Id: Ia19a6f8198f5584052e4a71bca4c4503ee088c1d
@tonyketcham
tonyketcham merged commit 1555464 into main May 11, 2026
19 checks passed
@tonyketcham
tonyketcham deleted the toeknee/proof-model-params-da19 branch May 11, 2026 02:20

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

COMMENT — Two persistent MED-severity concerns remain open across multiple prior runs (bare Cursor.models.list() and canvas template type duplication); all prior HIGH findings are now fully addressed. Test coverage for new exported functions is incomplete but not a blocker given the thoroughness of resolveModelSelectionFromCatalog tests.

Chunk-bound feedback

packages/proof/src/run_dag.ts:530-536

  • severity: MED
  • flagged-by: proof-runtime-internals, correctness-and-contracts, docs-and-positioning (3 independent runs)
  • risk: await Cursor.models.list() is called bare — a transient network error, rate-limit, or expired API key produces an unhandled rejection that terminates the process before any task runs, with a cryptic stack trace rather than an actionable message.
  • minimal fix: Wrap in try/catch and rethrow with a clear message: "[proof] failed to fetch model catalog — check CURSOR_API_KEY and network: <err.message>".

packages/proof/src/canvas_writer.ts:236-245

  • severity: LOW
  • flagged-by: proof-runtime-internals, correctness-and-contracts (2+ runs)
  • risk: ModelParameterValue and ModelSelection are redeclared inside the canvas template string. TypeScript cannot enforce parity with dag.ts; a new required field added to ModelSelection will silently not appear in canvas rendering until a runtime mismatch is noticed.
  • minimal fix: Add a snapshot/integration test that round-trips a parameterised TaskState through initialRunState → canvas render → check that params appear in the output string, so the drift is caught automatically.

Consensus findings

  • validateModelMap plain-string rejection — FULLY ADDRESSED. Prior runs (bc-ff4, bc-8712ac) flagged that validateModelMap would reject string model IDs. The current PR correctly calls normalizeModelSelection (which accepts both string | ModelSelection) instead of validateModelSelection. Tests validateModelMap accepts plain string model ids and validateModelMap accepts model selection objects with params confirm correctness. Thread PRRT_kwDOGV8TsM6A87Sm resolved.

  • scoreVariant/chooseMatchingVariant tie-breaking — FULLY ADDRESSED. Tests for highest-scoring variant, equal-score tie-break to catalog default, and no-match error are present in dag.test.ts.

  • validateModelId naming — FULLY ADDRESSED. Renamed to validateNonEmptyString, eliminating the misleading model-ID-specific name.

Disputed findings

None.

Coverage plan

  1. packages/proof/src/dag.test.ts — positive: createCatalogBackedModelResolver returns cloned selections and caches per complexity (call resolver twice for same complexity, verify same value but distinct object reference)
  2. packages/proof/src/dag.test.ts — negative: createCatalogBackedModelResolver propagates catalog errors (unknown model id → thrown error reaches caller)
  3. packages/proof/src/dag.test.ts — positive: formatModelSelection renders model-only as bare id string and model+params as "id (k=v, k=v)" format
  4. packages/proof/src/dag.test.ts — edge: normalizeModelSelection with { id: 'x', params: [] } is treated as a no-params selection (same as { id: 'x' })
  5. packages/proof/src/dag.test.ts — positive: isPauseTask / isOracleTask type guards return correct boolean for each kind
  6. packages/proof/src/dag.test.ts — negative: createModelSelectionResolver throws for unknown complexity string (e.g. 'EXTREME')

Suggested follow-ups

  • Cursor.models.list() error handling (MED, carried from 3 prior runs): wrap the startup catalog fetch in a try/catch with a user-friendly error message.
  • Canvas template type drift: consider a build-time code-generation step or at minimum a snapshot test to catch ModelSelection drift between dag.ts and the embedded template.
  • parseDAG structural validation tests: duplicate task IDs, unknown depends_on references, and cycle detection have no test coverage — these paths exist but are not exercised.

Reviewer scoreboard

  • proof-runtime-internals: partial output (runner timeout), ~6 findings surfaced, 4 coverage gaps, signal:HIGH — identified caching redundancy, legacy fallback safety, formatModelSelection persistence risk
  • test-coverage-robustness: partial output (runner timeout), ~8 coverage gaps identified, signal:HIGH — thorough enumeration of untested exported functions
  • correctness-and-contracts: partial output (runner timeout), signal:MED — confirmed normalizeModelSelection backward compat; canvas type drift flagged
  • docs-and-positioning: completed, 2 LOW findings, 3 coverage gaps, signal:MED — README migration note gap identified
Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

@@ -504,9 +537,23 @@ async function main(): Promise<void> {
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor.models.list() called with no error handling — 3rd consecutive run flagging this (still open).

A transient network error, rate-limit, or expired CURSOR_API_KEY produces an unhandled rejection here, killing the entire run before any task executes. The user sees a bare stack trace with no actionable guidance.

Suggested fix:

let catalog: Awaited<ReturnType<typeof Cursor.models.list>>;
try {
  catalog = await Cursor.models.list();
} catch (err) {
  throw new Error(
    `[proof] failed to fetch Cursor model catalog — check CURSOR_API_KEY and network: ${
      err instanceof Error ? err.message : String(err)
    }`
  );
}

type TaskKind = 'task' | 'pause' | 'oracle';

// Keep in sync with ModelParameterValue / ModelSelection in dag.ts.
interface ModelParameterValue {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Canvas template types still manually duplicated — compile-time sync enforcement missing.

ModelParameterValue and ModelSelection are redeclared inside the embedded template string. TypeScript cannot enforce parity with dag.ts; a new required field (e.g. weight?: number) added to ModelSelection in dag.ts will silently not appear in the canvas render until a runtime mismatch surfaces.

A snapshot/integration test that exercises initialRunState with a parameterised task and asserts the rendered params string would catch this drift automatically.

Comment thread packages/proof/src/dag.ts
};
}

export function createCatalogBackedModelResolver(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createCatalogBackedModelResolver has zero test coverage despite being new production code.

This function wraps the base resolver with caching and catalog validation. The cache correctness (returns clones, distinct object references per call) and error propagation (unknown model id bubbles to caller) are both untested.

Suggested additions to dag.test.ts:

  • Positive: call resolver twice for same complexity → same value, distinct reference (clone check)
  • Negative: catalog missing the model id → error reaches caller
  • Edge: all three complexity levels resolved consistently when overrides mix string + object forms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants